﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using Castle.DynamicProxy;

namespace NMock2.Internal
{
	/// <summary>
	/// <see cref="MessageGrabber"/> records calls made to its <see cref="Target"/> object.
	/// </summary>
	internal class InterfaceMessageGrabber<T> : RealProxy, IDisposable
	{
		public InterfaceMessageGrabber()
			: base(typeof(T))
		{ }

		private List<MethodCall> calls = new List<MethodCall>();
		public override IMessage Invoke(IMessage msg)
		{
			//wrap the msg in a MethodCall
			MethodCall call = new MethodCall(msg);

			//add the call to our list
			calls.Add(call);

			//create a default return value
			object ret = new object();

			//check if msg is a message intended for a method
			IMethodMessage methodMsg = msg as IMethodMessage;
			if (methodMsg != null)
			{
				//check if the method base is a MethodInfo
				MethodInfo info = methodMsg.MethodBase as MethodInfo;
				if (info != null)
				{
					if (
						info.ReturnType == typeof(void)
						)
					{
						//do nothing
					}
					else if (
						info.ReturnType.IsInterface
						)
					{
						ret = new FakeProxy(info.ReturnType).GetTransparentProxy();
					}
					else if (
						info.ReturnType.IsArray
						)
					{
						ret = Array.CreateInstance(info.ReturnType.GetElementType(), 0);
					}
					else if (
						info.ReturnType.IsEnum
						)
					{
						ret = Enum.GetValues(info.ReturnType).GetValue(0);
					}
					else if (
						info.ReturnType.IsPrimitive ||
						info.ReturnType == typeof(string)
						)
					{
						ret = Convert.ChangeType(0, info.ReturnType);
					}
					//else if (
					//    info.ReturnType.IsValueType //struct
					//    )
					//{
					//    ret = 
					//}
					else
					{
						try
						{
							ret = Activator.CreateInstance(info.ReturnType);
						}
						catch (MissingMethodException ex)
						{
							throw new Exception("Could not instantiate " + info.ReturnType.FullName, ex);
						}
					}
				}
			}

			//no out args
			object[] outArgs = new object[0];

			//return our out message
			return new ReturnMessage(ret, outArgs, outArgs.Length, call.LogicalCallContext, call);
		}

		/// <summary>
		/// An ordered list of all the calls "played" into the <see cref="MessageGrabber"/>.
		/// </summary>
		public List<MethodCall> Calls { get { return calls; } }

		/// <summary>
		/// A Remoting TransparentProxy object that can record calls made to it's members.
		/// </summary>
		public T Target
		{
			get { return (T)GetTransparentProxy(); }
		}

		public void Dispose()
		{
			calls.Clear();
		}
	}

	internal class FakeProxy : RealProxy
	{
		public FakeProxy(Type type)
			: base(type)
		{ }

		#region Overrides of RealProxy

		public override IMessage Invoke(IMessage msg)
		{
			throw new NotImplementedException();
		}

		#endregion
	}

	internal class ClassMessageGrabber<T> : ICallLog, IDisposable
		where T : class
	{
		private MessageInterceptor interceptor;

		public T Target { get; private set; }
		private List<IInvocation> calls = new List<IInvocation>();
		private ProxyGenerator gen;

		public ClassMessageGrabber()
		{
			interceptor = new MessageInterceptor(this);
			gen = new ProxyGenerator();
			ProxyGenerationOptions options = new ProxyGenerationOptions(new MessageHook());
			options.AddMixinInstance(new object());
			Target = gen.CreateClassProxy<T>(options, interceptor);


			//gen = new ProxyGenerator();
			//Target = gen.CreateInterfaceProxyWithoutTarget(typeof (T), new MessageInterceptor(this)) as T;
		}

		/// <summary>
		/// An ordered list of all the calls "played" into the <see cref="MessageGrabber"/>.
		/// </summary>
		public List<IInvocation> Calls { get { return calls; } }

		public void AddCall(IInvocation invocation)
		{
			calls.Add(invocation);
		}

		public void Dispose()
		{
			//TODO:
		}
	}

	internal interface ICallLog
	{
		void AddCall(IInvocation invocation);
	}

	internal class MessageInterceptor : IInterceptor
	{
		private readonly ICallLog _callLog;

		public MessageInterceptor(ICallLog callLog)
		{
			_callLog = callLog;
		}

		public void Intercept(IInvocation invocation)
		{
			_callLog.AddCall(invocation);
			invocation.Proceed();
		}
	}

	internal class MessageHook : IProxyGenerationHook
	{
		public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
		{
			return true;
		}

		//For NETFX40 interface impl
		public void NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
		{
			NonVirtualMemberNotification(type, memberInfo);
		}

		public void NonVirtualMemberNotification(Type type, MemberInfo memberInfo)
		{
			//TODO: check on this based on the new DynProxy impl
			throw new ExpectationException(string.Format("A call to the {0} member was captured but could not be mocked because it is not declared as virtual (Overridable in VB).  Consider making this member virtual or as part of an interface that this type implements.", memberInfo.Name));
		}

		public void MethodsInspected()
		{
		}

		public override bool Equals(object obj)
		{
			return ((obj != null) && (obj.GetType() == typeof(MessageHook)));
		}

		public override int GetHashCode()
		{
			return base.GetType().GetHashCode();
		}

	}
}
